﻿namespace Summoner
{
    using UnityEngine;
    using UnityEditor;
    using System.Collections.Generic;
    using System;
    using System.IO;
    using System.Reflection;

    public class PublisherDialogue : EditorWindow
    {
        private static PublisherDialogue _window = null;
        private const string PresetName = "BuildPreset";
        public string AssetConfigName = "AssetNames";
        private int presetSelect = 0;
        private int currentPreset = 0;
        private BuildTargetGroup buildTargetGroup = BuildTargetGroup.iOS;
        private bool copyBundle = false;
        private BuildOptions buildOption = BuildOptions.None;
        private List<PresetData> presets = null;
        private string[] buildOptionArrary = Enum.GetNames(typeof(BuildOptions));
        private string DefaultSetting = "";
        private Dictionary<string, AssetInfo> _name2package = new Dictionary<string, AssetInfo>();
        private AssetVessel _vessel = null;
        private bool _tryBuildBundle = false;
        private bool _tryPublish = false;

        [MenuItem("Apple/Publish Window...", false, 51)]
        private static void Init()
        {
            EditorWindow.GetWindow<PublisherDialogue>();
        }

        void OnEnable()
        {
            _window = this;
            _window.titleContent = new GUIContent("Publish Window");
            _window.maxSize = new Vector2(350, 205);
            _window.minSize = _window.maxSize;
            AssetDatabase.ImportAsset(AssetPath.AssetVesselPath, ImportAssetOptions.ForceSynchronousImport);
            this._vessel = AssetDatabase.LoadAssetAtPath(AssetPath.AssetVesselPath, typeof(AssetVessel)) as AssetVessel;
            this.InitPreset();
        }

        private void InitPreset()
        {
            if (this.presets != null)
            {
                this.presets.Clear();
                this.presets = null;
            }
            this.DefaultSetting = PlayerPrefs.GetString("Default", "Default");
            if (File.Exists(Path.Combine(AssetPath.ConfigPath, PresetName + ".json")))
            {
                this.presets = JsonSerializer.UnpackJsonFromFile<List<PresetData>>(AssetPath.ConfigPath, PresetName);
                for (int i = 0; i < this.presets.Count; i++)
                {
                    if (this.presets[i].Name.CompareTo(this.DefaultSetting) == 0)
                    {
                        this.SetOptions(this.presets[i]);
                        this.presetSelect = i;
                        break;
                    }
                }
            }
            else
            {
                this.buildTargetGroup = BuildTargetGroup.iOS;
                this.presets = new List<PresetData>();
                this.presets.Add(new PresetData("Default", "iOS", true, new List<int>() { 0 }));
                this.DefaultSetting = "Default";
                this.SetOptions(this.presets[0]);
            }
            this.currentPreset = this.presetSelect;
        }

        private void SetOptions(PresetData data)
        {
            this.buildTargetGroup = (BuildTargetGroup)Enum.Parse(typeof(BuildTargetGroup), data.Platform, true);
            this.buildOption = new BuildOptions();
            for (int i = 0; i < data.Options.Count; i++)
            {
                this.buildOption |= (BuildOptions)(Mathf.Pow(2, data.Options[i]));
            }
        }

        private List<string> GetPresetNames()
        {
            List<string> names = new List<string>();
            if (this.presets != null)
            {
                for (int i = 0; i < this.presets.Count; i++)
                {
                    names.Add(this.presets[i].Name);
                }
            }
            return names;
        }

        void OnGUI()
        {
            GUILayout.BeginHorizontal();
            #region Build Parameters
            GUILayout.BeginVertical("box");
            GUILayout.Label("Target: ");
            this.buildTargetGroup = (BuildTargetGroup)EditorGUILayout.EnumPopup(this.buildTargetGroup);
            GUILayout.Label("Option: ");
            this.buildOption = (BuildOptions)EditorGUILayout.EnumMaskPopup("", buildOption);
            GUILayout.Space(6);
            this.copyBundle = GUILayout.Toggle(this.copyBundle, new GUIContent("Copy bundle to StreamAssets"));
            GUILayout.EndVertical();
            #endregion
            #region Preset
            GUILayout.BeginVertical("box");
            GUILayout.Label("Preset: ");
            this.presetSelect = EditorGUILayout.Popup(this.presetSelect, this.GetPresetNames().ToArray());
            this.DefaultSetting = EditorGUILayout.TextField(this.DefaultSetting);
            if (this.currentPreset != this.presetSelect)
            {
                this.currentPreset = this.presetSelect;
                this.DefaultSetting = this.GetPresetNames()[this.presetSelect];
                this.SetOptions(this.presets[this.presetSelect]);
                PlayerPrefs.SetString("Default", this.DefaultSetting);
            }
            if (GUILayout.Button("Save Preset"))
            {
                if (!string.IsNullOrEmpty(this.DefaultSetting))
                {
                    if (!this.GetPresetNames().Contains(this.DefaultSetting))
                    {
                        this.presets.Add(new PresetData(this.DefaultSetting, this.buildTargetGroup.ToString(), this.copyBundle, this.GetSelectedNumber((int)buildOption, buildOptionArrary.Length)));
                    }
                    else
                    {
                        if (EditorUtility.DisplayDialog("提示！", "预制已存在，是否覆盖？", "Yes", "No"))
                        {
                            this.presets[this.presetSelect].Platform = this.buildTargetGroup.ToString();
                            this.presets[this.presetSelect].Options.Clear();
                            List<int> temps = this.GetSelectedNumber((int)buildOption, buildOptionArrary.Length);
                            for (int i = 0; i < temps.Count; i++)
                            {
                                this.presets[this.presetSelect].Options.Add(temps[i]);
                            }
                        }
                    }
                    JsonSerializer.PackJsonToFile(AssetPath.ConfigPath, this.presets, PresetName);
                    PlayerPrefs.SetString("Default", this.DefaultSetting);
                    AssetDatabase.Refresh();
                    this.InitPreset();
                }
            }
            if (GUILayout.Button("Delete Preset"))
            {
                for (int i = 0; i < this.presets.Count; i++)
                {
                    if (this.DefaultSetting.CompareTo(this.presets[i].Name) == 0 && (this.DefaultSetting.CompareTo("Default") != 0))
                    {
                        this.presets.RemoveAt(i);
                        JsonSerializer.PackJsonToFile(AssetPath.ConfigPath, this.presets, PresetName);
                        this.presetSelect = 0;
                        this.currentPreset = this.presetSelect;
                        AssetDatabase.Refresh();
                        break;
                    }
                }
            }
            GUILayout.EndVertical();
            #endregion
            GUILayout.EndHorizontal();

            if ((this.buildTargetGroup == BuildTargetGroup.iOS) || (this.buildTargetGroup == BuildTargetGroup.tvOS))
            {
                EditorUserBuildSettings.iOSBuildConfigType = (iOSBuildType)EditorGUILayout.EnumPopup("Development Build", EditorUserBuildSettings.iOSBuildConfigType, new GUILayoutOption[0]);
                EditorUserBuildSettings.symlinkLibraries = EditorGUILayout.Toggle("Symlink Unity libraries", EditorUserBuildSettings.symlinkLibraries, new GUILayoutOption[0]);
                GUILayout.Space(18);
            }
            if (this.buildTargetGroup == BuildTargetGroup.Android)
            {
                EditorUserBuildSettings.androidBuildSubtarget = (MobileTextureSubtarget)EditorGUILayout.EnumPopup("Texture Compression", EditorUserBuildSettings.androidBuildSubtarget, new GUILayoutOption[0]);
                EditorUserBuildSettings.androidBuildSystem = (AndroidBuildSystem)EditorGUILayout.EnumPopup("Build System", EditorUserBuildSettings.androidBuildSystem, new GUILayoutOption[0]);
                EditorUserBuildSettings.androidBuildType = (AndroidBuildType)EditorGUILayout.EnumPopup("Development Build", EditorUserBuildSettings.androidBuildType, new GUILayoutOption[0]);
            }

            #region function buttons
            GUILayout.BeginHorizontal();
            if (GUILayout.Button("Build Bundle"))
            {
                this._tryBuildBundle = true;
            }
            if (GUILayout.Button("Build Player"))
            {
                this.BuildPlayer();
            }
            GUILayout.EndHorizontal();
            if (GUILayout.Button("Publish"))
            {
                if (!AssetPath.UseResources)
                {
                    this.ClearReaourcesFolder();
                    this._tryPublish = true;
                }
                else
                {
                    this.ClearStreamAssetsFolder();
                    this.BuildResourcesFolder();
                    string[] files = Directory.GetFiles(AssetPath.ResourcesPath);
                    if (files.Length == 0)
                    {
                        EditorUtility.DisplayDialog("错误！", "Resources目录不能为空！", "确认");
                        return;
                    }
                    this.BuildPlayer();
                }
                AssetDatabase.Refresh();
            }
            if (this._tryBuildBundle)
            {
                this._tryBuildBundle = false;
                this.BuildBundles();
            }
            if (this._tryPublish)
            {
                this._tryPublish = false;
                this.Publish();
            }
            #endregion
        }

        private void ClearReaourcesFolder()
        {
            if (Directory.Exists(AssetPath.ResourcesPath))
            {
                Directory.Delete(AssetPath.ResourcesPath, true);
            }
            Directory.CreateDirectory(AssetPath.ResourcesPath);
        }

        private void BuildResourcesFolder()
        {
            this.ClearReaourcesFolder();
            if (this._vessel != null)
            {
                foreach (AssetInfo info in this._vessel.assetList)
                {
                    string source = info.pathInEditor;
                    string destination = Path.Combine(AssetPath.ResourcesPath, Path.GetFileName(info.pathInEditor));
                    if (!File.Exists(destination))
                    {
                        File.Copy(source, destination);
                    }
                }
                GameConfigInfo gameConfig = new GameConfigInfo() { version = this._vessel.version, platform = Application.platform, language = SystemLanguage.Chinese, assets = this._vessel.assetList };
                JsonSerializer.PackJsonToFile(AssetPath.ResourcesPath, gameConfig, AssetPath.AssetConfigName.ToLower());
            }
        }

        private void ClearStreamAssetsFolder()
        {
            if (Directory.Exists(AssetPath.PublishPath))
            {
                Directory.Delete(AssetPath.PublishPath, true);
            }
            Directory.CreateDirectory(AssetPath.PublishPath);
        }

        private void BuildBundles()
        {
            if (this._vessel != null)
            {
                BundleFunction.GenerateAssetBundle(this._vessel.assetList, this.GetSelectedBuildTarget(this.buildTargetGroup));
                if (this.copyBundle) { this.CopyBundleToStreamFolder(); }
                GameConfigInfo gameConfig = new GameConfigInfo() { version = this._vessel.version, platform = Application.platform, language = SystemLanguage.Chinese, assets = this._vessel.assetList };
                JsonSerializer.PackJsonToFile(AssetPath.PublishPath, gameConfig, AssetPath.AssetConfigName.ToLower());
            }
        }

        private void CopyBundleToStreamFolder()
        {
            string[] files = Directory.GetFiles(AssetPath.PublishPath, "*.*", System.IO.SearchOption.TopDirectoryOnly);
            if (!Directory.Exists(AssetPath.StreamingAssetsPath))
            {
                Directory.CreateDirectory(AssetPath.StreamingAssetsPath);
            }
            if (files.Length > 0)
            {
                foreach (string file in files)
                {
                    if (Path.GetExtension(file).ToLower() != ".manifest")
                    {
                        File.Copy(file, Path.Combine(AssetPath.StreamingAssetsPath, Path.GetFileName(file)), true);
                    }
                    else if (file.ToLower().Contains(AssetPath.StreamFolderName.ToLower()))
                    {
                        File.Copy(file, Path.Combine(AssetPath.StreamingAssetsPath, Path.GetFileName(file)), true);
                    }
                }
                GameConfigInfo gameConfig = new GameConfigInfo() { version = this._vessel.version, platform = Application.platform, language = SystemLanguage.Chinese, assets = this._vessel.assetList };
                JsonSerializer.PackJsonToFile(AssetPath.StreamingAssetsPath, gameConfig, AssetPath.AssetConfigName.ToLower());
            }
        }

        public void BuildPlayer()
        {
            if (Directory.Exists(this.BuildPath()))
            {
                Directory.Delete(this.BuildPath(), true);
            }
            Directory.CreateDirectory(this.BuildPath());
            try
            {
                PlayerSettings.stripEngineCode = false;
                string[] scenes = this.GetLevelsFromBuildSettings();
                //BuildOptions BuildOp = ConvertToUnityBuildOption(this.buildOption);
#if UNITY_5_6
                EditorUserBuildSettings.SwitchActiveBuildTarget(this.buildTargetGroup, this.GetSelectedBuildTarget(buildTargetGroup));
#else
                EditorUserBuildSettings.SwitchActiveBuildTarget(this.GetSelectedBuildTarget(buildTargretGroup));
#endif
                if((this.buildOption & BuildOptions.ShowBuiltPlayer) != BuildOptions.ShowBuiltPlayer)
                {
                    buildOption |= BuildOptions.ShowBuiltPlayer;
                }
                if(this.buildTargetGroup == BuildTargetGroup.iOS)
                {
                    if ((this.buildOption & BuildOptions.AcceptExternalModificationsToPlayer) != BuildOptions.AcceptExternalModificationsToPlayer)
                    {
                        buildOption |= BuildOptions.AcceptExternalModificationsToPlayer;
                    }
                    if ((this.buildOption & BuildOptions.Il2CPP) != BuildOptions.Il2CPP)
                    {
                        buildOption |= BuildOptions.Il2CPP;
                    }
                    PlayerSettings.SetScriptingBackend(BuildTargetGroup.iOS, ScriptingImplementation.IL2CPP);
                    PlayerSettings.SetArchitecture(BuildTargetGroup.iOS, 2);
                }

                if (this.buildTargetGroup == BuildTargetGroup.Android)
                {
                    //PlayerSettings.Android.keystoreName = "";
                    //PlayerSettings.Android.keystorePass = "";
                    //PlayerSettings.Android.keyaliasName = "";
                    //PlayerSettings.Android.keyaliasPass = "";
                    if ((this.buildOption & BuildOptions.Il2CPP) == BuildOptions.Il2CPP)
                    {
                        PlayerSettings.SetScriptingBackend(BuildTargetGroup.Android, ScriptingImplementation.IL2CPP);
                    }
                    else
                    {
                        PlayerSettings.SetScriptingBackend(BuildTargetGroup.Android, ScriptingImplementation.Mono2x);
                    }
                }

                //nrgctx-trampolines=2048 这是留给递归泛型使用的空间，默认是 1024
                //nimt-trampolines=4096 这是留给接口使用的空间，默认是 128
                //ntrampolines=2048 这是留给泛型方法调用使用的空间，默认是 1024
                PlayerSettings.aotOptions = "ntrampolines=2048,nrgctx-trampolines=2048,nimt-trampolines=4096";
                BuildPipeline.BuildPlayer(scenes, this.GetBuildTargetName(this.GetSelectedBuildTarget(this.buildTargetGroup), this.buildOption), this.GetSelectedBuildTarget(this.buildTargetGroup), this.buildOption);
                Debug.Log("Built!, File: " + this.BuildPath());
            }
            catch (Exception e)
            {
                Debug.LogException(e);
            }
        }

        private void Publish()
        {
            this.BuildBundles();
            this.BuildPlayer();
        }

        private string[] GetLevelsFromBuildSettings()
        {
            List<string> levels = new List<string>();
            for (int i = 0; i < EditorBuildSettings.scenes.Length; ++i)
            {
                if (EditorBuildSettings.scenes[i].enabled)
                {
                    levels.Add(EditorBuildSettings.scenes[i].path);
                }
            }
            return levels.ToArray();
        }

        private BuildOptions ConvertToUnityBuildOption(CustomBuildOptions option)
        {
            BuildOptions buildOp = BuildOptions.None;
            List<string> selects = this.GetSelectedItems(this.GetSelectedNumber((int)option, this.buildOptionArrary.Length).ToArray(), this.buildOptionArrary);
            for (int i = 0; i < selects.Count; i++)
            {
                buildOp |= (BuildOptions)Enum.Parse(typeof(BuildOptions), selects[i], true);
            }
            return buildOp;
        }

        public List<int> GetSelectedNumber(int mask, int length)
        {
            List<int> group = new List<int>();
            for (int i = 0; i < length; i++)
            {
                int layer = 1 << i;
                if ((mask & layer) != 0)
                {
                    group.Add(i);
                }
            }
            return group;
        }

        public List<string> GetSelectedItems(int[] group, string[] items)
        {
            List<string> names = new List<string>();
            for (int i = 0; i < group.Length; i++)
            {
                names.Add(items[group[i]]);
            }
            return names;
        }

        private BuildTarget GetSelectedBuildTarget(BuildTargetGroup buildTargetGroup)
        {
            switch (buildTargetGroup)
            {
                case BuildTargetGroup.WebPlayer:
                    if (EditorUserBuildSettings.webPlayerStreamed)
                    {
                        return BuildTarget.WebPlayerStreamed;
                    }
                    return BuildTarget.WebPlayer;

                case BuildTargetGroup.Standalone:
                    return EditorUserBuildSettings.selectedStandaloneTarget;

                case BuildTargetGroup.iOS:
                    return BuildTarget.iOS;

                case BuildTargetGroup.Android:
                    return BuildTarget.Android;

                case BuildTargetGroup.tvOS:
                    return BuildTarget.tvOS;

                case BuildTargetGroup.Tizen:
                    return BuildTarget.Tizen;

                case BuildTargetGroup.XBOX360:
                    return BuildTarget.XBOX360;

                case BuildTargetGroup.XboxOne:
                    return BuildTarget.XboxOne;

                case BuildTargetGroup.PS3:
                    return BuildTarget.PS3;

                case BuildTargetGroup.PSP2:
                    return BuildTarget.PSP2;

                case BuildTargetGroup.PS4:
                    return BuildTarget.PS4;

                case BuildTargetGroup.WSA:
                    return BuildTarget.WSAPlayer;

                case BuildTargetGroup.WebGL:
                    return BuildTarget.WebGL;

                case BuildTargetGroup.SamsungTV:
                    return BuildTarget.SamsungTV;

                case BuildTargetGroup.Facebook:
                    PropertyInfo property = typeof(EditorUserBuildSettings).GetProperty("selectedFacebookTarget", BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic);
                    return (BuildTarget)property.GetValue(null, null);

                default:
                    return EditorUserBuildSettings.activeBuildTarget;
            }
        }

        private string GetBuildTargetName(BuildTarget target, BuildOptions BuildOp)
        {
            switch (target)
            {
                case BuildTarget.iOS:
                    return this.BuildPath() + "/" + GetProjectName();
                case BuildTarget.Android:
                    if((BuildOp & BuildOptions.AcceptExternalModificationsToPlayer) == BuildOptions.AcceptExternalModificationsToPlayer)
                    {
                        return this.BuildPath() + "/" + GetProjectName();
                    }
                    else
                    {
                        return this.BuildPath() + "/" + GetProjectName() + ".apk";
                    }
                case BuildTarget.StandaloneWindows:
                case BuildTarget.StandaloneWindows64:
                    return this.BuildPath() + "/" + GetProjectName() + ".exe";
                case BuildTarget.StandaloneOSXIntel:
                case BuildTarget.StandaloneOSXIntel64:
                case BuildTarget.StandaloneOSXUniversal:
                    return this.BuildPath() + "/" + GetProjectName() + ".app";
#if !UNITY_5
                case BuildTarget.WebPlayer:
                case BuildTarget.WebPlayerStreamed:
                    return this.BuildPath() + "/" + GetProjectName();
#endif
                // Add more build targets for your own.
                default:
                    Debug.Log("Target not implemented.");
                    return null;
            }
        }

        private string GetCurrentDate()
        {
            return DateTime.Now.Year + "-" + DateTime.Now.Month + "-" + DateTime.Now.Day + "-" + DateTime.Now.Hour + "-" + DateTime.Now.Minute + "-" + DateTime.Now.Second;
        }

        private string GetProjectName()
        {
            string[] s = Application.dataPath.Split('/');
            return (s[s.Length - 2] + "-" + GetCurrentDate());
        }

        private string BuildPath()
        {
            switch (this.GetSelectedBuildTarget(this.buildTargetGroup))
            {
                case BuildTarget.iOS:
                    return "builds/IOS";
                case BuildTarget.Android:
                    return "builds/Android";
#if !UNITY_5
                case BuildTarget.WP8Player:
                    return "builds/Wp8";
#endif
                case BuildTarget.WSAPlayer:
                    return "builds/WinStore";
                case BuildTarget.StandaloneWindows:
                    return "builds/Win";
                case BuildTarget.StandaloneWindows64:
                    return "builds/Win64";
                case BuildTarget.StandaloneOSXIntel:
                    return "builds/Mac";
                case BuildTarget.StandaloneOSXIntel64:
                    return "builds/Mac64";
                case BuildTarget.StandaloneOSXUniversal:
                    return "builds/MacUniversal";
#if !UNITY_5
                case BuildTarget.WebPlayer:
                    return "builds/WebPlayer";
                case BuildTarget.WebPlayerStreamed:
                    return "builds/WebPlayerStream";
#endif
            }
            return "builds";
        }

        /// <summary>
        /// edit the curve editting window
        /// </summary>
        public static void OpenWindow()
        {
            Init();
        }

        void OnDisable()
        {
            _window = null;
        }

        public static PublisherDialogue instance { get { return _window; } }
    }

    public class PresetData
    {
        public string Name;
        public string Platform;
        public bool CopyBundle;
        public List<int> Options;

        public PresetData(string name, string platform, bool copyBundle, List<int> options)
        {
            this.Name = name;
            this.Platform = platform;
            this.CopyBundle = copyBundle;
            this.Options = options;
        }
    }

    public enum CustomBuildOptions
    {
        /// <summary>
		///   <para>Perform the specified build without any special settings or extra tasks.</para>
		/// </summary>
		None = 0,
        /// <summary>
        ///   <para>Build a development version of the player.</para>
        /// </summary>
        Development = 1,
        /// <summary>
        ///   <para>Run the built player.</para>
        /// </summary>
        AutoRunPlayer = 4,
        /// <summary>
        ///   <para>Show the built player.</para>
        /// </summary>
        ShowBuiltPlayer = 8,
        /// <summary>
        ///   <para>Build a compressed asset bundle that contains streamed scenes loadable with the WWW class.</para>
        /// </summary>
        BuildAdditionalStreamedScenes = 16,
        /// <summary>
        ///   <para>Used when building Xcode (iOS) or Eclipse (Android) projects.</para>
        /// </summary>
        AcceptExternalModificationsToPlayer = 32,
        InstallInBuildFolder = 64,
        /// <summary>
        ///   <para>Copy UnityObject.js alongside Web Player so it wouldn't have to be downloaded from internet.</para>
        /// </summary>
        [Obsolete("WebPlayer has been removed in 5.4")]
        WebPlayerOfflineDeployment = 128,
        /// <summary>
        ///   <para>Start the player with a connection to the profiler in the editor.</para>
        /// </summary>
        ConnectWithProfiler = 256,
        /// <summary>
        ///   <para>Allow script debuggers to attach to the player remotely.</para>
        /// </summary>
        AllowDebugging = 512,
        /// <summary>
        ///   <para>Symlink runtime libraries when generating iOS Xcode project. (Faster iteration time).</para>
        /// </summary>
        SymlinkLibraries = 1024,
        /// <summary>
        ///   <para>Don't compress the data when creating the asset bundle.</para>
        /// </summary>
        UncompressedAssetBundle = 2048,
        /// <summary>
        ///   <para>Sets the Player to connect to the Editor.</para>
        /// </summary>
        ConnectToHost = 4096,
        /// <summary>
        ///   <para>Build headless Linux standalone.</para>
        /// </summary>
        EnableHeadlessMode = 16384,
        /// <summary>
        ///   <para>Build only the scripts of a project.</para>
        /// </summary>
        BuildScriptsOnly = 32768,
        Il2CPP = 65536,
        /// <summary>
        ///   <para>Include assertions in the build. By default, the assertions are only included in development builds.</para>
        /// </summary>
        ForceEnableAssertions = 131072,
        /// <summary>
        ///   <para>Use chunk-based Lz4 compression when building the Player.</para>
        /// </summary>
        CompressWithLz4 = 262144,
        /// <summary>
        ///   <para>Force full optimizations for script complilation in Development builds.</para>
        /// </summary>
        ForceOptimizeScriptCompilation = 524288,
        ComputeCRC = 1048576,
        /// <summary>
        ///   <para>Do not allow the build to succeed if any errors are reporting during it.</para>
        /// </summary>
        StrictMode = 2097152
    }
}